package com.yunji.system.config.mybatis;

/**
 * @Project: hg-hwc
 * @Package: org.jeecg.config.mybatis
 * @Title: SQLLogsInterceptor
 * @Description: SQL语句日志拦截器
 * <p>
 * History:
 * Date                     Version     Author          Summary
 * ============================================================
 * 2024-01-20 11:59:07      V1.0        HuaAo       新建类
 */

import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.uuid.UUIDUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;

import static com.yunji.common.constant.Constants.*;


//@Component
//@Intercepts({
//        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
//        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
//        @Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })
//})
public class SQLLogsInterceptor implements Interceptor {
    private static final Logger logs = LoggerFactory.getLogger(SQLLogsInterceptor.class);

    private static final String MAPPING_STATEMENT = "delegate.mappedStatement";
    private static final String FIELD_CONFIG = "configuration";
    private static final String FIELD_TYPE_HANDLE = "typeHandlerRegistry";

    // 制表符换行符正则表达式
    private static final String REX_TABS_LINE_SYMBOL = "[\\t\\n\\x0B\\f\\r]";
    // 匹配多个空格正则表达式
    private static final String REX_SPACE = " +";
    // 匹配括号左侧多个空格正则表达式
    private static final String REX_L_BRACKET = " *\\( *";
    // 匹配括号右侧多个空格正则表达式
    private static final String REX_R_BRACKET = " *\\) *";
    // 匹配逗号两侧多个空格正则表达式
    private static final String REX_R_COMMA_SYMBOL = " *, *";
    // 匹配问号正则表达式
    private static final String REX_QUESTION_SYMBOL = "\\?";
    // 匹配回车和换行符正则表达式
    private static final String REX_ENTER_LINE_SYMBOL = "(\r?\n(\\s*\r?\n)*+)";

    private static final String STR_R_BRACKET_SPACE = ") ";
    private static final String STR_COMMA_SPACE = ", ";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        MetaObject mObject = SystemMetaObject.forObject(invocation.getTarget());
        MappedStatement mappedStatement = (MappedStatement) mObject.getValue(MAPPING_STATEMENT);
        // 执行的mapper statement ID
        String mapper = mappedStatement.getId();
        // 获取当前的开始时间戳
        long startTime = System.currentTimeMillis();
        // 记录当前时间
        String time = handleTime(new Date());
        StatementHandler handler = (StatementHandler) target;
        try {
            return invocation.proceed();
        } finally {
            String id = UUIDUtils.quickUUID();
            logs.debug("=====>【{}】 at 【{}】 Begin Execute SQL: {}", id, time, mapper);
            handle(handler, id, startTime);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // Do nothing.
    }

    /**
     * @return String 时间对应字符串
     * @Title handleTime
     * @Desc 初始时间为字符串
     * @Date 2024-01-20 15:23:49
     * @Author HuaAo
     * @Param d 时间信息
     */
    private String handleTime(Date d) {
        String str = STR_NULL;
        if (d == null) {
            return str;
        }

        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            str = sdf.format(d);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    /**
     * @Title handle
     * @Desc 处理SQL语句信息
     * @Date 2024-01-20 15:25:32
     * @Author HuaAo
     * @Param state 执行语句处理器
     * @Param id 执行语句唯一标识
     * @Param begin 开始执行时间
     */
    private void handle(StatementHandler state, String id, long begin) {
        try {
            // SQL的执行的时间
            long consume = System.currentTimeMillis() - begin;

            // 获取绑定的SQL对象
            BoundSql bound = state.getBoundSql();
            // 得到需要执行的SQL语句,并进行格式
            String sql = format(bound.getSql());

            // 获取参数处理器
            ParameterHandler handle = state.getParameterHandler();

            Field configurationField = handle.getClass().getDeclaredField(FIELD_CONFIG);
            Field typeHandlerRegistryField = handle.getClass().getDeclaredField(FIELD_TYPE_HANDLE);


            if (configurationField == null || typeHandlerRegistryField == null) {
                return;
            }

            // 设置私有属性可访问
            configurationField.setAccessible(true);
            // 设置私有属性可访问
            typeHandlerRegistryField.setAccessible(true);

            Configuration config = (Configuration) configurationField.get(handle);
            TypeHandlerRegistry types = (TypeHandlerRegistry) typeHandlerRegistryField.get(handle);

            String params = getParams(bound, config, types);

            logs.debug("=====> Execute 【{}】 SQL: 【{}】", id, this.merge(sql, params.split(STR_COMMA)));
            logs.debug("=====>【{}】 Finish Execute Consume Time: 【{}ms】", id, consume);
        } catch (Exception e) {
            logs.error("处理SQL失败", e);
        }
    }

    /**
     * @return String 参数字符串
     * @Title getParams
     * @Desc 获取语句参数信息
     * @Date 2024-01-20 15:28:36
     * @Author HuaAo
     * @Param bound SQL保存对象
     * @Param config 参数配置
     * @Param types 参数类型处理注册器
     */
    private String getParams(BoundSql bound, Configuration config, TypeHandlerRegistry types) {
        //处理sql的参数，该部分参考的是DefaultParameterHandler中setParameters方法中的实现
        StringBuilder build = new StringBuilder();

        // SQL的参数对象
        Object param = bound.getParameterObject();
        // 需要绑定的参数映射对象
        List<ParameterMapping> mappingList = bound.getParameterMappings();

        if (CollectionUtils.isEmpty(mappingList)) {
            return build.toString();
        }

        for (ParameterMapping parameterMapping : mappingList) {
            // 如果该参数不是输出参数,则进行处理
            if (ParameterMode.OUT == parameterMapping.getMode()) {
                continue;
            }

            Object value;
            // 参数的名字,属性
            String prop = parameterMapping.getProperty();

            // 先从附加的,主要是list、array等的处理
            if (bound.hasAdditionalParameter(prop)) {
                value = bound.getAdditionalParameter(prop);
            } else if (param == null) {
                value = null;
            } else if (types.hasTypeHandler(param.getClass())) {
                // typeHandlerRegistry注册了某个类的处理
                value = param;
            } else {
                // 默认的MetaObject 的处理,根据参数获取值
                MetaObject meta = config.newMetaObject(param);
                value = meta.getValue(prop);
            }

            if (value instanceof Date) {
                // 如果是日期，则格式化一下
                value = handleTime((Date) value);
            }

            build.append(STR_COMMA).append(value);
        }

        // 删除第一个逗号
        build.deleteCharAt(0);
        return build.toString();
    }

    /**
     * @return String 处理后SQL语句
     * @Title format
     * @Desc 格式化SQL语句
     * @Date 2024-01-20 16:00:36
     * @Author HuaAo
     * @Param sql SQL语句
     */
    private String format(String sql) {
        // 输入sql字符串空判断
        if (BeanUtils.isNull(sql)) {
            return STR_NULL;
        }

        return sql.replaceAll(REX_TABS_LINE_SYMBOL, STR_NULL)      // 回车换行制表符等替换成空
                .replaceAll(REX_SPACE, STR_SPACE)                   // 连续多个空格替换成1个空格
                .replaceAll(REX_L_BRACKET, STR_L_BRACKET)           // 去掉左括号的空格
                .replaceAll(REX_R_BRACKET, STR_R_BRACKET_SPACE)     // 去掉右括号的空格
                .replaceAll(REX_R_COMMA_SYMBOL, STR_COMMA_SPACE);   // 去掉逗号左右两个的空格
    }

    /**
     * @return String 合并后SQL语句
     * @Title merge
     * @Desc 合并SQL语句和参数
     * @Date 2024-01-20 15:59:50
     * @Author HuaAo
     * @Param sql SQL执行语句
     * @Param objects 参数数组
     */
    public String merge(String sql, Object[] objects) {
        if (objects == null || sql == null) {
            return STR_NULL;
        }

        List<Object> arrays = Arrays.asList(objects);
        List<Object> params = new ArrayList<>(arrays);

        while (sql.contains(STR_QUESTION) && !CollectionUtils.isEmpty(params) && objects.length > 0) {
            Object param = params.get(0);
            if (param instanceof String) {
                // 处理字符中含$和\符号
                String sub = Matcher.quoteReplacement("'" + param + "'");
                sql = sql.replaceFirst(REX_QUESTION_SYMBOL, sub);
            } else if (null != param) {
                // 处理字符中含$和\符号
                String sub = param.toString();
                sql = sql.replaceFirst(REX_QUESTION_SYMBOL, Matcher.quoteReplacement(sub));
            }
            params.remove(0);
        }

        return sql.replaceAll(REX_ENTER_LINE_SYMBOL, "\r\n");
    }
}